package com.biboheart.adapter.core.impl;

import com.biboheart.adapter.compiler.ast.Node;
import com.biboheart.adapter.compiler.enums.NodeType;
import com.biboheart.adapter.compiler.token.Token;
import com.biboheart.adapter.core.Collector;
import com.biboheart.adapter.core.Composition;
import com.biboheart.adapter.core.Packager;
import com.biboheart.adapter.model.StructureNode;
import com.biboheart.adapter.support.enums.LogicalEnum;
import com.biboheart.adapter.support.enums.OperatorEnum;
import com.biboheart.adapter.support.enums.ValueTypeEnum;
import com.biboheart.adapter.support.utils.*;
import com.fasterxml.jackson.core.JsonProcessingException;

import java.util.*;

public class NormalPackager implements Packager {
    private final Map<String, Object> data = new HashMap<>();
    private final Map<String, Object> dataHash = new HashMap<>();
    private final Map<String, String> objectContrast = new HashMap<>();
    private final Map<String, StructureNode> pathContrast = new HashMap<>();

    private final Composition composition;
    private final Collector collector;

    public NormalPackager(Composition composition, Collector collector) {
        this.composition = composition;
        this.collector = collector;
        createObjectHash(null, null, null);
        try {
            System.out.println("target data hash:" + JsonUtils.obj2json(dataHash));
            System.out.println("object contrast:" + JsonUtils.obj2json(objectContrast));
            System.out.println("path contrast:" + JsonUtils.obj2json(pathContrast));
            System.out.println("target data:" + JsonUtils.obj2json(data));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        generateValue();
    }

    @Override
    public Object result() {
        if (data.isEmpty()) return null;
        return data.get("root");
    }

    @SuppressWarnings("unchecked")
    private void createObjectHash(String parentTargetPath, String parentSourcePath, StructureNode node) {
        if (null == node) {
            node = composition.getNode("root");
        }
        if (null == node) {
            return;
        }
        String targetName = node.getTargetName();
        if (null == targetName) {
            return;
        }
        ValueTypeEnum valueType = node.getValueType();
        String path = null == parentTargetPath ? node.getPath() : parentTargetPath + "." + targetName;
        String sourceName = node.getSourceName();
        if (null == sourceName) {
            return;
        }
        String sourcePath = null == parentSourcePath ? node.getSourcePath() : parentSourcePath + "." + sourceName;
        Object object;
        if (ValueTypeEnum.VALUE_TYPE_LIST.equals(valueType)) {
            object = collector.getObject(sourcePath);
            if (null == object) {
                dataHash.put(path, null);
                return;
            }
            List<StructureNode> children = node.getChildren();
            if (null == children || children.isEmpty()) {
                dataHash.put(path, null);
                return;
            }
            List<Map<String, Object>> list = new ArrayList<>();
            dataHash.put(path, list);
            objectContrast.put(path, sourcePath);
            pack(path, list);
            List<Object> objectList = (object instanceof List) ? ObjectTypeUtils.convertType(object, List.class) : new ArrayList<>(Collections.singletonList(object));
            for (int i = 0; i < objectList.size(); i ++) {
                Map<String, Object> map = new HashMap<>();
                list.add(map);
                String _path = path + "[" + i + "]";
                String _sourcePath = sourcePath + "[" + i + "]";
                dataHash.put(_path, map);
                objectContrast.put(_path, _sourcePath);
                pathContrast.put(_path, node);
                for (StructureNode child : children) {
                    if (!ValueTypeEnum.VALUE_TYPE_MAP.equals(child.getValueType()) && !ValueTypeEnum.VALUE_TYPE_LIST.equals(child.getValueType())) {
                        continue;
                    }
                    createObjectHash(_path, _sourcePath, child);
                }
            }
        } else if (ValueTypeEnum.VALUE_TYPE_MAP.equals(valueType)) {
            object = collector.getObject(sourcePath);
            if (null == object) {
                dataHash.put(path, null);
                return;
            }
            List<StructureNode> children = node.getChildren();
            if (null == children || children.isEmpty()) {
                dataHash.put(path, null);
                return;
            }
            pathContrast.put(path, node);
            Map<String, Object> map = new HashMap<>();
            dataHash.put(path, map);
            objectContrast.put(path, sourcePath);
            pack(path, map);
            for (StructureNode child : children) {
                if (!ValueTypeEnum.VALUE_TYPE_MAP.equals(child.getValueType()) && !ValueTypeEnum.VALUE_TYPE_LIST.equals(child.getValueType())) {
                    continue;
                }
                createObjectHash(path, sourcePath, child);
            }
        }
    }

    private void generateValue() {
        if (pathContrast.isEmpty()) {
            return;
        }
        Set<Map.Entry<String, StructureNode>> entrySet = pathContrast.entrySet();
        for (Map.Entry<String, StructureNode> entry : entrySet) {
            String path = entry.getKey();
            String sourcePath = objectContrast.get(path);
            StructureNode node = entry.getValue();
            List<StructureNode> children = node.getChildren();
            if (null == children || children.isEmpty()) {
                continue;
            }
            Object sourceObject = collector.getObject(sourcePath);
            if (null == sourceObject) {
                continue;
            }
            for (StructureNode child : children) {
                String sourceName = child.getSourceName();
                String targetName = child.getTargetName();
                if (!child.getSourceObjectName().equals(node.getSourcePath())) {
                    sourcePath = child.getSourceObjectName();
                    sourceObject = collector.getObject(sourcePath);
                }
                if (null == targetName) {
                    continue;
                }
                if (null == sourceName) {
                    pack(path + "." + child.getTargetName(), null);
                }
                if (ValueTypeEnum.VALUE_TYPE_MAP.equals(child.getValueType()) || ValueTypeEnum.VALUE_TYPE_LIST.equals(child.getValueType())) {
                    continue;
                }
                Node ast = null;
                if (null != child.getAst()) {
                    ast = child.getAst().getChildren().get(0);
                }
                pack(path + "." + child.getTargetName(), calcValue(ast, sourceObject, sourcePath, sourceName));
            }
        }
    }

    private Object calcValue(Node ast, Object sourceObject, String sourcePath, String sourceName) {
        if (null == ast) {
            return ObjectUtils.getValue(sourceObject, sourceName);
        }
        NodeType nodeType = ast.getNodeType();
        Token token = ast.getToken();
        if (NodeType.NODE_CONSTANT.equals(nodeType)) {
            return token.getVal();
        }
        if (NodeType.NODE_VARIABLE.equals(nodeType)) {
            String tokenPath = String.valueOf(token.getVal());
            String tokenObjectPath;
            String tokenSourceName = String.valueOf(token.getVal());
            if (tokenPath != null && tokenPath.contains(".")) {
                tokenObjectPath = tokenPath.substring(0, tokenPath.lastIndexOf("."));
                tokenSourceName = tokenPath.substring(tokenPath.lastIndexOf(".") + 1);
                if (!sourcePath.replaceAll("\\[\\d+]", "").equals(tokenObjectPath)) {
                    sourceObject = collector.getObject(tokenObjectPath);
                }
            }
            if (null == tokenSourceName) {
                tokenSourceName = sourceName;
            }
            if (null == sourceObject) {
                return null;
            } else {
                return ObjectUtils.getValue(sourceObject, tokenSourceName);
            }
        }
        List<Node> children = ast.getChildren();
        if (null == children || children.isEmpty()) {
            return null;
        }
        List<Object> objectList = new ArrayList<>();
        for (Node child : children) {
            objectList.add(calcValue(child, sourceObject, sourcePath, sourceName));
        }
        Object value = null;
        if (NodeType.NODE_COMMAND.equals(nodeType)) {
            String instructName = token.getCode();
            Object input = objectList.get(0);
            Object[] arr;
            if (objectList.size() > 1) {
                arr = new Object[objectList.size() - 1];
                for (int i = 1; i < objectList.size(); i ++) {
                    arr[i - 1] = objectList.get(i);
                }
            } else {
                arr = new Object[0];
            }
            value = ActuatorUtils.execute(instructName, input, arr);
        } else if (NodeType.NODE_OPERATOR.equals(nodeType)) {
            Object left = objectList.get(0);
            Object right = objectList.size() > 1 ? objectList.get(1) : null;
            if (LogicalEnum.OPERATOR_LOGICAL_NOT.code().equals(token.getCode())) {
                value = LogicalUtils.execute(LogicalEnum.OPERATOR_LOGICAL_NOT.code(), ObjectTypeUtils.convertType(left, Boolean.class));
            } else if (OperatorEnum.OPERATOR_BIT_INV.code().equals(token.getCode())) {
                value = CalculateUtils.execute(OperatorEnum.OPERATOR_BIT_INV.code(), ObjectTypeUtils.convertType(left, Number.class));
            } else if (LogicalEnum.contains(token.getCode())) {
                value = LogicalUtils.execute(token.getCode(), left, right);
            } else if (OperatorEnum.contains(token.getCode())) {
                value = CalculateUtils.execute(token.getCode(), ObjectTypeUtils.convertType(left, Number.class), ObjectTypeUtils.convertType(right, Number.class));
            }
        }
        return value;
    }

    private void pack(String path, Object value) {
        String fieldName;
        String objectPath;
        if (!path.contains(".")) {
            fieldName = path;
            objectPath = null;
        } else {
            fieldName = path.substring(path.lastIndexOf(".") + 1);
            objectPath = path.substring(0, path.lastIndexOf("."));
        }
        if (fieldName.contains("[") || fieldName.contains("]")) {
            return;
        }
        if (null == objectPath) {
            data.put(fieldName, value);
            return;
        }
        Map<String, Object> objectMap = getObject(data, objectPath);
        if (null == objectMap) {
            return;
        }
        objectMap.put(fieldName, value);
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> getObject(Map<String, Object> parentObj, String path) {
        if (null == path || "".equals(path)) {
            return null;
        }
        String name = path.contains(".") ? path.substring(0, path.indexOf(".")) : path;
        String nextPath = path.contains(".") ? path.substring(path.indexOf(".") + 1) : null;
        Integer index = null;
        boolean isList = false;
        if (name.contains("[")) {
            index = Integer.parseInt(name.substring(name.indexOf("[") + 1, name.indexOf("]")));
            name = name.substring(0, name.indexOf("["));
            isList = true;
        }
        if (null == parentObj) {
            parentObj = data;
        }
        Object obj = parentObj.get(name);
        if (null == obj) {
            return null;
        }
        List<Map<String, Object>> list;
        Map<String, Object> map;
        if (isList) {
            list = ObjectTypeUtils.convertType(obj, List.class);
            if (list.size() <= index) {
                return null;
            }
            map = list.get(index);
        } else {
            map = ObjectTypeUtils.convertType(obj, Map.class);
        }
        if (null != nextPath) {
            return getObject(map, nextPath);
        }
        return map;
    }
}
